﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CSharpExperiments.Nullsafety
{
	public static class Sure
	{
		public static Sure<A> From<A>(A val)
		{
			return new Sure<A>(val);
		}

		public static bool IsSureValue<A>(A val)
		{
			return val != null;
		}
	}

	/// <summary>A wrapper that always contains a non-null value. Compare to Maybe/Nullable.</summary>
	/// <remarks>With implicit conversions between A and Sure(A), simple null checking can be applied
	/// to a method simply by making the parameter types Sure(A):
	///		<code>
	///		Sure(String) SomeMethod(Sure(String) str, Sure(Object) obj, Sure(Whatever) whatever)
	///		{
	///			return str + obj.ToString() + whatever.ToString();
	///		}
	///		</code>
	///	No need to null-check - exception will be thrown on implicit conversion to Sure(String), etc.
	///	and return value is gauranteed (if no exception thrown) to be not-null and is implicitly
	///	assignable to a reference of type String.
	/// </remarks>
	public struct Sure<A>
	{
		public A Value { get; private set; }

		internal Sure(A val) : this()
		{
			if (! Sure.IsSureValue(val))
				throw new NullReferenceException("Can't create a Sure with a null value");

			Value = val;
		}

		public static implicit operator A(Sure<A> certain)
		{
			return certain.Value;
		}

		/// <summary>Implicit conversion allows A to be passed where Sure(A) is expected.
		/// If the given A is null, exception will be thrown immediately.</summary>
		public static implicit operator Sure<A>(A val)
		{
			return Sure.From(val);
		}

		public Type WrappedType { get { return typeof(A); } }

		public Sure<B> Select<B>(Func<A, B> func)
		{
			return Sure.From(func(Value));
		}

		public Sure<B> SelectMany<B>(Func<A, Sure<B>> func)
		{
			return func(Value);
		}

		public Maybe<A> Where(Func<A, bool> filter)
		{
			return filter(Value) ? Maybe.From(Value) : Maybe.None<A>();
		}

		public void ForEach(Action<A> action)
		{
			action(Value);
		}

		public Sure<B> Cast<B>()
		{
			return Sure.From((B) Convert.ChangeType(Value, typeof(B)));
		}

		public Maybe<B> OfType<B>()
		{
			return (Value is B) ? Maybe.From((B) Convert.ChangeType(Value, typeof(B))) : Maybe.None<B>();
		}

		public Maybe<A> AsMaybe()
		{
			return Maybe.From(Value);
		}

		public override String ToString()
		{
			return Value.ToString();
		}

		public override int GetHashCode()
		{
			return (int) (Value.GetHashCode() ^ 0x58585858);
		}

		public override bool Equals(Object other)
		{
			if (! (other is Sure<A>)) return false;
			Sure<A> that = (Sure<A>) other;
			return Object.Equals(this.Value, that.Value);
		}
	}
}
